#include "../include/OpenChaoJi_secc.h"


enum OpenChaoJi_secc_utility_session_status {
    OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_DISCONNECT = 0,      // 断开连接
    OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_START_CONNECTING,    // 开始连接(传输层)
    OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_TRASPORT_CONNECTED,  // 传输层连接成功, 开始连接链路层
    OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_LINKAGE_CONNECTED,   // 链路层连接成功
};

/**
 * @brief 本模块的上下文数据结构, 内部包含传输层, 数据队列, 链路层三个模块的句柄
 */
struct OpenChaoJi_secc_context_utility_session {
    int tag;

    enum OpenChaoJi_secc_utility_session_status status; // 当前状态

    enum OpenChaoJi_secc_gbt27930_type secc_gbt27930_type;

    struct OpenChaoJi_context_utility_data_buff * context_data_buff;
    struct OpenChaoJi_context_utility_transport * context_transport;

    struct OpenChaoJi_secc_context_utility_linkage * context_linkage;
};

static void OpenChaoJi_secc_utility_session_switch_status(struct OpenChaoJi_secc_context_utility_session* context, enum OpenChaoJi_secc_utility_session_status new_status)
{
    const char * c_session_status_names[4] = {
        "DISCONNECT",
        "START_CONNECTING",
        "TRASPORT_CONNECTED",
        "LINKAGE_CONNECTED",
    };

    if (context->status != new_status)
    {
        LOG_NOTICE("[%d]status %s->%s", context->tag, c_session_status_names[context->status], c_session_status_names[new_status]);
        context->status = new_status;
    }
}

/**
 * @brief 初始化
 *	      1. 创建数据队列句柄, 用于准备收发数据
 *        2. 创建传输层句柄, 用于进行底层数据通信
 *        3. 创建链路层句柄, 用于进行链路协商和链路监测(A类系统不需要)
 *
 * @param info 初始化信息
 * @return 成功返回创建的上下文指针，失败返回NULL。
 */
struct OpenChaoJi_secc_context_utility_session * OpenChaoJi_secc_utility_session_init(const struct OpenChaoJi_secc_create_info_utility_session * info)
{
    struct OpenChaoJi_secc_context_utility_session * context_session = (struct OpenChaoJi_secc_context_utility_session *)OpenChaoJi_os_malloc(sizeof(struct OpenChaoJi_secc_context_utility_session));
    if (context_session == NULL)
    {
        LOG_CRIT("session malloc fail");
        return NULL;
    }
    context_session->tag = info->tag;
    context_session->secc_gbt27930_type = info->secc_gbt27930_type;

    context_session->context_data_buff = OpenChaoJi_utility_data_buff_init(&info->data_buff);
    if (context_session->context_data_buff == NULL)
    {
        goto FAIL_SESSION;
    }

    context_session->context_linkage = OpenChaoJi_secc_utility_linkage_init(&info->linkage);
    if (context_session->context_linkage == NULL)
    {
        goto FAIL_SESSION;
    }

    struct OpenChaoJi_create_info_utility_transport transport_info;

    transport_info.tag = info->tag;
#if (OPENCHAOJI_CONFIG_ENABLE_GBT27930_B == CONFIG_YES)
    if (info->secc_gbt27930_type == OPENCHAOJI_SECC_GBT27930_TYPE_B)
    {
		transport_info.dest_addr = OPENCHAOJI_GBT27930_B_CAN_ADDR_EVCC;
		transport_info.src_addr = OPENCHAOJI_GBT27930_B_CAN_ADDR_SECC;
        transport_info.transport_protocol_type = OPENCHAOJI_TRANSPORT_PROTOCOL_TYPE_GBT27930_B;
    }
    else
#endif
#if (OPENCHAOJI_CONFIG_ENABLE_GBT27930_A == CONFIG_YES)
    {
		transport_info.dest_addr = OPENCHAOJI_GBT27930_A_CAN_ADDR_EVCC;
		transport_info.src_addr = OPENCHAOJI_GBT27930_A_CAN_ADDR_SECC;
        transport_info.transport_protocol_type = OPENCHAOJI_TRANSPORT_PROTOCOL_TYPE_GBT27930_A;
    }
#endif
    transport_info.can_send_func = OpenChaoJi_secc_hal_information_send_can_voluation;
    transport_info.can_recv_func = OpenChaoJi_secc_hal_information_recv_can_voluation;
    transport_info.param = info->hal_handle;

	context_session->context_transport = OpenChaoJi_utility_transport_init(&transport_info);
    if (context_session->context_linkage == NULL)
    {
        goto FAIL_SESSION;
    }

    context_session->status = OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_DISCONNECT;

    return context_session;

FAIL_SESSION:
    LOG_CRIT("session init fail");
    if (context_session != NULL)
    {
        if (context_session->context_data_buff != NULL)
        {
            OpenChaoJi_utility_data_buff_term(context_session->context_data_buff);
        }
        if (context_session->context_linkage != NULL)
        {
            OpenChaoJi_secc_utility_linkage_term(context_session->context_linkage);
        }
        if (context_session->context_transport != NULL)
        {
            OpenChaoJi_utility_transport_term(context_session->context_transport);
        }

        OpenChaoJi_os_free(context_session);
    }

    return NULL;
}

/**
 * @brief 销毁, 销毁链路层/传输层/数据队列句柄
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @return 成功返回0，失败返回-1。
 */
int OpenChaoJi_secc_utility_session_term(struct OpenChaoJi_secc_context_utility_session * context)
{
    if (context->context_linkage != NULL)
    {
        OpenChaoJi_secc_utility_linkage_term(context->context_linkage);
    }

    OpenChaoJi_utility_transport_term(context->context_transport);
    OpenChaoJi_utility_data_buff_term(context->context_data_buff);

    OpenChaoJi_os_free(context);

    return 0;
}

/**
 * @brief 复位, 复位链路层/传输层/数据队列句柄
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @return 成功返回0，失败返回-1。
 */
int OpenChaoJi_secc_utility_session_reset(struct OpenChaoJi_secc_context_utility_session * context)
{
    OpenChaoJi_utility_data_buff_reset(context->context_data_buff);
    OpenChaoJi_utility_transport_reset(context->context_transport);
    OpenChaoJi_secc_utility_linkage_reset(context->context_linkage);

    return 0;
}

/**
 * @brief 启动数据通信, system在pilot导引确认后开始启动数据通信
 *        1. 传输层启动连接(connect或者listen/accept), 非阻塞
 *        2. 传输层连接成功后, 链路层依赖传输层进行链路协商(A类系统不需要)
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @return 成功返回0，失败返回-1。
 */
int OpenChaoJi_secc_utility_session_start(struct OpenChaoJi_secc_context_utility_session* context)
{
    // 这里是否需要判断当前状态已启动?
    if (context->status >= OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_TRASPORT_CONNECTED)
    {
        OpenChaoJi_secc_utility_session_stop(context);
    }

    OpenChaoJi_utility_transport_connect(context->context_transport);

    OpenChaoJi_secc_utility_session_switch_status(context, OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_START_CONNECTING);

    return 0;
}

/**
 * @brief 停止数据通信, system停止流程或者进入故障时需要断开连接
 *        1.
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @return 成功返回0，失败返回-1。
 */
int OpenChaoJi_secc_utility_session_stop(struct OpenChaoJi_secc_context_utility_session* context)
{
    LOG_ALERT("[%d]Session Reset", context->tag);

    // TODO: 这里暂时用reset代替stop, 需要讨论是否需要增加单独的stop方法
    OpenChaoJi_utility_data_buff_reset(context->context_data_buff);
    OpenChaoJi_utility_transport_reset(context->context_transport);
    OpenChaoJi_secc_utility_linkage_stop(context->context_linkage);

    OpenChaoJi_secc_utility_session_switch_status(context, OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_DISCONNECT);

    return 0;
}

/**
 * @brief 写入数据, system运行时, 如果需要发送数据给evcc, 则通过本方法写入, 实际是传递给数据队列句柄
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @param send_data 待发送的数据结构
 * @return 读到数据返回0，读不到数据返回-1。
 */
int OpenChaoJi_secc_utility_session_data_write(struct OpenChaoJi_secc_context_utility_session *context, struct OpenChaoJi_utility_data_buff_send_data * send_data)
{
    return OpenChaoJi_utility_data_buff_data_write(context->context_data_buff, send_data, 0);
}

/**
 * @brief 读取数据, system运行时, 需要从本模块的读取有效数据, 一次读取一个包. 每次service运行会循环调用本函数读取, 直到读光. 实际是传递给数据队列句柄
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @param recv_data 待读取数据结构, 注意读取方需要自行free接收的数据区
 * @return 读到数据返回0，读不到数据返回-1。
 */
int OpenChaoJi_secc_utility_session_data_read(struct OpenChaoJi_secc_context_utility_session *context, struct OpenChaoJi_utility_data_buff_recv_data * recv_data)
{
    // 从数据缓冲区读取数据包, 一次读一个
    return OpenChaoJi_utility_data_buff_queue_read(context->context_data_buff, recv_data);
}

/**
 * @brief 接收数据服务, session存在异步处理, 也包含内部的链路处理, 所以每次service触发内部处理
 *        1. 调用传输层service, 使得传输层可以处理当前交互数据(例如消息应答等)
 *        2. 从传输层读取完整数据包
 *           1) 如果能读取到, 则跳转步骤3
 *           2) 如果不能读取到, 则进入步骤4
 *        3. 处理完整数据包
 *           1) 如果是链路层相关数据包, 则交给链路模块处理(OpenChaoJi_secc_utility_linkage_got_data)
 *           2) 其他数据包交给数据队列模块暂存(OpenChaoJi_utility_data_buff_queue_write), 同时需要告知链路检测模块有数据到达(OpenChaoJi_secc_utility_linkage_got_data)
 *           3) 然后跳转到步骤1, 继续调用传输层service, 尝试读取下一个完整数据包
 *        4. 调用链路层serivce处理, 保证链路层能进行状态切换
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @return 无
 */
void OpenChaoJi_secc_utility_session_service_try_recv(struct OpenChaoJi_secc_context_utility_session * context)
{
    int count = 0;
	int rtn = 0;
    struct OpenChaoJi_utility_data_buff_recv_data tmp_data;

    //  传输层一次只能处理一个消息，且会同时生成一个消息和一个链路层， 所以每次调用传输层后， 必须读空消息
	// 1. 调用传输层service, 使得传输层可以处理当前交互数据(例如消息应答等)
	OpenChaoJi_utility_transport_service(context->context_transport);

    do
    {
        // 2. 从传输层读取数据
        rtn = OpenChaoJi_utility_transport_recv(context->context_transport, &tmp_data);

        if (rtn >= 0)
        {
            count++;
            tmp_data.recv_tick = OpenChaoJi_os_get_ticks();

            //底层CAN数据帧
            if (rtn == 0)
            {
                // 2）如果是CAN数据帧，则更新链路接收最新数据帧的ticks，并且通过链路层检测是否版本协商数据帧，如果是版本协商数据帧则交给链路层处理
                uint32_t last_ticks = OpenChaoJi_os_get_ticks();
                OpenChaoJi_secc_utility_linkage_revc_data_ticks(context->context_linkage, last_ticks);
                rtn = OpenChaoJi_secc_utility_linkage_is_linkage_data(context->context_linkage, &tmp_data);
                if (rtn == 1)
                {
                    OpenChaoJi_secc_utility_linkage_got_data(context->context_linkage, &tmp_data);
                }

                // 链路层数据和ACK数据等都需要在此判断释放, 防止内存泄漏
                if (!tmp_data.recv_data.src_free)
                {
                    OpenChaoJi_os_free(tmp_data.recv_data.data);
                }
            }
            else
            {
            	// 2) 其他消息类型完整数据包则交给数据队列模块暂存(OpenChaoJi_utility_data_buff_queue_write)
                if (tmp_data.tag.gbt.msg_type != OPENCHAOJI_GBT27930_SYSTEM_B_LINKAGE_DATA)
                {
                	OpenChaoJi_utility_data_buff_queue_write(context->context_data_buff, &tmp_data);
                }
            }

            // 3) 然后跳转到步骤1, 继续调用传输层service, 尝试读取下一个完整数据包
            continue;
        }
        else
        {
            break;
        }

    } while (count < 10);

    // 4. 调用链路层serivce处理, 保证链路层能进行状态切换
    OpenChaoJi_secc_utility_linkage_service(context->context_linkage);

    return;
}


static int secc_utility_session_write_func(void *param, struct OpenChaoJi_utility_data_buff_send_data * send_data)
{
    struct OpenChaoJi_context_utility_transport * context_transport = (struct OpenChaoJi_context_utility_transport *)param;

    return OpenChaoJi_utility_transport_send(context_transport, send_data);
}

/**
 * @brief 发送数据服务, serivce每次运行system后会再调用本方法触发数据区确认到期数据自动发送(OpenChaoJi_utility_data_buff_service_try_send)
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @return 无
 */
void OpenChaoJi_secc_utility_session_service_try_send(struct OpenChaoJi_secc_context_utility_session* context)
{
	OpenChaoJi_secc_utility_linkage_send_data_ticks(context->context_linkage, OpenChaoJi_os_get_ticks());

    OpenChaoJi_utility_data_buff_service_try_send(context->context_data_buff, secc_utility_session_write_func, context->context_transport);
}

/**
 * @brief 会话层连接是否已经建立, 如连接未建立, 或已断开, 则system需要根据超时情况决定状态跳转
 * 		  1. 传输层已经建立连接, 或连接未断开
 *        2. 数据链路层已经完成版本协商 或链路检测未进入故障状态
 *
 * @param context 系统上下文指针，包含必要的环境信息。
 * @return 成功返回0，失败返回-1。
 */
enum OpenChaoJi_secc_utility_session_connect_status OpenChaoJi_secc_utility_session_is_connected(struct OpenChaoJi_secc_context_utility_session* context)
{
    if (context->status >= OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_LINKAGE_CONNECTED)
    {
        return OPENCHAOJI_SECC_UTILITY_SESSION_CONNECT_STATUS_CONNECTED;
    }
    else if (context->status == OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_DISCONNECT)
    {
        return OPENCHAOJI_SECC_UTILITY_SESSION_CONNECT_STATUS_DISCONNECTED;
    }
    else
    {
        return OPENCHAOJI_SECC_UTILITY_SESSION_CONNECT_STATUS_CONNECTING;
    }
}

/**
 * @brief: 会话层服务的状态机处理方法, 需要由系统定时触发, 也可立即触发，一般建议不超过20ms调用一次
 *         注意, 由于兼容非系统方式实现, 所以本项目所有方法默认处于同一线程, 如厂家通过操作系统实现, 厂家需要保证线程安全
 * @param {OpenChaoJi_secc_context_utility_session *} context
 * @return {*}
 */
void OpenChaoJi_secc_utility_session_service(struct OpenChaoJi_secc_context_utility_session* context)
{
    // 不管当前处于任何状态, 如果传输层断开, 则需要断开会话层连接
    if (!OpenChaoJi_utility_transport_is_connected(context->context_transport))
    {
        OpenChaoJi_secc_utility_session_switch_status(context, OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_DISCONNECT);
    }

    // 开始连接状态, 因为传输层断开上面已经判断, 这里即可认为传输层已经连接
    // 需要触发链路层操作, 或者就行链路版本协商, 或者进行链路检测
    if (context->status == OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_START_CONNECTING)
    {
    	LOG_DEBUG("[%d]linkage start", context->tag);
        OpenChaoJi_secc_utility_linkage_start(context->context_linkage, secc_utility_session_write_func, context->context_transport);
        OpenChaoJi_secc_utility_session_switch_status(context, OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_TRASPORT_CONNECTED);
    }

    // 链路层需要触发service来收发链路报文
    if (context->status >= OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_TRASPORT_CONNECTED)
    {
        OpenChaoJi_secc_utility_linkage_service(context->context_linkage);
    }

    // 判断链路层连接结果来切换会话层状态
    enum OpenChaoJi_secc_utility_linkage_version_confirm_step confirm_step = OpenChaoJi_secc_utility_linkage_get_version_confer_state(context->context_linkage);
    if (confirm_step == OPENCHAOJI_SECC_LINKAGE_VERSION_CONFER_STEP_SUCCEED)
    {
        OpenChaoJi_secc_utility_session_switch_status(context, OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_LINKAGE_CONNECTED);
    }
	else if(confirm_step == OPENCHAOJI_SECC_LINKAGE_VERSION_CONFER_STEP_FAILED)
	{
		OpenChaoJi_secc_utility_session_stop(context);
	}

    // 链路异常状态, 需要重新连接
    enum OpenChaoJi_secc_utility_linkage_link_check_step check_step = OpenChaoJi_secc_utility_linkage_get_link_check_state(context->context_linkage);
    if (check_step == OPENCHAOJI_SECC_LINKAGE_LINK_CHECK_STEP_ABNORMAL)
    {
        OpenChaoJi_secc_utility_session_switch_status(context, OPENCHAOJI_SECC_UTILITY_SESSION_STATUS_START_CONNECTING);
    }

    return;
}


